package com.atguigu.gulimall.product.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.gulimall.product.service.CategoryBrandRelationService;
import com.atguigu.gulimall.product.vo.Catelog2Vo;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

//    @Autowired
//    CategoryDao categoryDao;

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedissonClient redissonClient;


    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }


    @Override
    public List<CategoryEntity> listWithTree() {
        //1、查出所有分类
        List<CategoryEntity> entities = baseMapper.selectList(null);

        //2、组装成父子的树形结构

        //2.1）、找到所有的一级分类
        List<CategoryEntity> level1Menus = entities.stream().filter(categoryEntity ->
                categoryEntity.getParentCid() == 0
        ).map((menu)->{
            menu.setChildren(getChildrens(menu,entities));
            return menu;
        }).sorted((menu1,menu2)->{
            return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
        }).collect(Collectors.toList());




        return level1Menus;
    }

    @Override
    public void removeMenuByIds(List<Long> asList) {
        //TODO  1、检查当前删除的菜单，是否被别的地方引用

        //逻辑删除
        baseMapper.deleteBatchIds(asList);
    }

    //[2,25,225]
    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        List<Long> parentPath = findParentPath(catelogId, paths);

        Collections.reverse(parentPath);


        return parentPath.toArray(new Long[parentPath.size()]);
    }

    /**
     * 级联更新所有关联的数据
     * @param category
     */
    @Caching(evict = {
         @CacheEvict(value = "category", key = "'getCatalogJson'"),
         @CacheEvict(value = "category",key = "'getLevel1Categorys'")
    }) //一次性执行多个Cache语句
    @CacheEvict(value="category",allEntries = true) //批量删除category下面的东西
    @Transactional
    @Override
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);
        categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
    }

    //225,25,2
    private List<Long> findParentPath(Long catelogId,List<Long> paths){
        //1、收集当前节点id
        paths.add(catelogId);
        CategoryEntity byId = this.getById(catelogId);
        if(byId.getParentCid()!=0){
            findParentPath(byId.getParentCid(),paths);
        }
        return paths;

    }


    //递归查找所有菜单的子菜单
    private List<CategoryEntity> getChildrens(CategoryEntity root,List<CategoryEntity> all){

        List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
            return categoryEntity.getParentCid() == root.getCatId();
        }).map(categoryEntity -> {
            //1、找到子菜单
            categoryEntity.setChildren(getChildrens(categoryEntity,all));
            return categoryEntity;
        }).sorted((menu1,menu2)->{
            //2、菜单的排序
            return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
        }).collect(Collectors.toList());

        return children;
    }
    // 会自动检查缓存中是否存在，不存在才调方法，存在直接不调方法了
    //每一个缓存都需要指定缓存到哪个名字下，缓存的分区，按业务类型value, key是当前名字下的分区 最后为 value::key
    //默认时间是-1，永不过期，需要定义过期时间,需要在配置文件中配置 time-to-live//ttl
    //将数据保存为json,其他语言也可以读取
    @Cacheable(value={"category"},key = "#root.method.name")
    @Override
    public List<CategoryEntity> getLevel1Categorys() {

        List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));

        return categoryEntities;
    }

    //堆外内存溢出异常
    //spring2.0以后默认使用lettuce作为操作redis的客户端，它使用netty进行网络通信
    //lettuce的bug导致堆外内存溢出 -Xmx300 netty如果没有指定堆外内存，netty就会使用-Xmx作为堆外内存；
    //出现的原因是没有得到内存释放，可以通过-Dio.netty.maxDirectMemory进行设置
    //解决方案1、升级lettuce 2、切换jedis jedis和lettuce都是操作redis的底层，Spring封装了jedis和lettuce

    //缓存穿透 去查询一个一定不存在的数据，缓存中不命中，100万的并发直接穿过缓存到达了数据库 导致数据库崩溃
    //解决方案 将null进行缓存，加上一个短暂的过期时间

    //缓存雪崩 缓存中数据在同一时间都过期，大面积失效，
    //过期时间加上一个随机的值，不让同一时间过期

    //缓存击穿 某一个key失效，然后同时大量的访问这个key，此时直达数据库，导致数据库崩溃，一般是针对于热点数据的
    //枷锁，先让一个人来读，放入缓存，然后在让其他人去读缓存，这样就解决了

    //1、加上空结果缓存，解决缓存穿透
    //2、加上随机过期时间，解决缓存雪崩
    //3、加锁，解决缓存击穿


    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedissonLock(){

        //缓存数据一致性
        //1、双写模式
        //2、失效模式

        //Redisson解决缓存击穿
        RLock lock = redissonClient.getLock("catalogJson-lock");
        lock.lock();

        //这里解决缓存击穿
        String catalogJson = stringRedisTemplate.opsForValue().get("CatalogJson");
        if(!StringUtils.isEmpty(catalogJson)){
            Map<String, List<Catelog2Vo>> parseObject = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {});
            return parseObject;
        }

        Map<String, List<Catelog2Vo>> dataFromDb;

        try{
            dataFromDb = getCatalogJson();
        }finally {
            lock.unlock();
        }
        return  dataFromDb;
    }

    @Cacheable(value = {"category"}, key = "#root.method.name")
    public Map<String, List<Catelog2Vo>> getCatalogJson() {

                //优化，将数据库的多次查询变为一次
                List<CategoryEntity> selectList = baseMapper.selectList(null);

                //查出所有分类
                //1、查到所有的一级分类
                //List<CategoryEntity> level1Categorys = selectList.stream().filter(item->{return item.getParentCid()==0;}).collect(Collectors.toList());
                List<CategoryEntity> level1Categorys = getParent_cid(selectList, 0);

                //查到所有一级分类的二级分类并封装为 Map<String, List<Catelog2Vo>>
                Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(
                        //拿到当前一级分类的id,k,v都是是当前的父节点
                        k -> k.getCatId().toString(),  //父节点id
                        v -> {                         //拿到当前父节点的子节点
//                    List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>()
//                            .eq("parent_cid", v.getCatId()));
                            List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
                            //封装成指定的二级分类
                            List<Catelog2Vo> catelog2Vos = null;
                            if (categoryEntities != null) {
                                catelog2Vos = categoryEntities.stream().map(l2 -> {
                                    Catelog2Vo catelog2Vo = new Catelog2Vo(
                                            v.getCatId().toString(),
                                            null,
                                            l2.getCatId().toString(),
                                            l2.getName());
                                    //找三级分离
                                    List<Catelog2Vo.Catalog3Vo> collect = null;
                                    //每次过滤都要过io太慢了
                                    //List<CategoryEntity> level3Catelog = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", l2.getCatId()));
                                    List<CategoryEntity> level3Catelog = getParent_cid(selectList, l2.getCatId());
                                    if (level3Catelog != null) {
                                        collect = level3Catelog.stream().map(l3 -> {
                                            Catelog2Vo.Catalog3Vo catalog3Vo = new Catelog2Vo.Catalog3Vo(
                                                    l2.getCatId().toString(),
                                                    l3.getName(),
                                                    l3.getCatId().toString());
                                            return catalog3Vo;
                                        }).collect(Collectors.toList());
                                    }
                                    catelog2Vo.setCatalog3List(collect);
                                    return catelog2Vo;
                                }).collect(Collectors.toList());
                            }
                            return catelog2Vos;
                        }));

                return parent_cid;
        }

    @Cacheable(value = "category", key = "#id")
    @Override
    public CategoryEntity selectById(Integer id) {
        CategoryEntity categoryEntity = baseMapper.selectById(id);
        return categoryEntity;
    }

    private List<CategoryEntity> getParent_cid (List < CategoryEntity > selectList,long parent_cid){
            List<CategoryEntity> collect = selectList.stream().filter(item -> {
                return item.getParentCid() == parent_cid;
            }).collect(Collectors.toList());
            return collect;
        }


}